package org.springframework.security.config.annotation.web.configurers;

import org.apache.commons.lang.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
* @author gx
* @since 2017/5/22 11:07
*/
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private static final String AUTH_TOKEN_NAME = "_auth_token";
    AuthenticationManager authenticationManager;
    public TokenAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(new RequestMatcher() {
            @Override
            public boolean matches(HttpServletRequest request) {
                if(StringUtils.isEmpty(request.getParameter(AUTH_TOKEN_NAME))) {
                    return false;
                }
                HttpSession session = request.getSession();
                // 如果需要登陆，肯定会有登陆错误信息
                if(session != null) {
                    AuthenticationException ex = (AuthenticationException) session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);

                    if(ex == null) {
                        return false;
                    }
                    else{
                        session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
                    }
                }
                Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();

                if(existingAuth == null && existingAuth.isAuthenticated()) {
                    return false;
                }
                return true;
            }
        });
        setAuthenticationManager(authenticationManager);
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws  IOException, ServletException {
       String header = request.getParameter(AUTH_TOKEN_NAME);
        String[] tokens = extractAndDecodeHeader(header);
        assert tokens.length == 2;

        String username = tokens[0];
        String password = tokens[1];
        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();

        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);
    }

    /**
     * Provided so that subclasses may configure what is put into the authentication request's details
     * property.
     *
     * @param request that an authentication request is being created for
     * @param authRequest the authentication request object that should have its details set
     */
    protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }
    private String[] extractAndDecodeHeader(String header) throws IOException {

        byte[] base64Token = header.substring(6).getBytes("UTF-8");
        byte[] decoded;
        try {
            decoded = Base64.decode(base64Token);
        } catch (IllegalArgumentException e) {
            throw new BadCredentialsException("Failed to decode basic authentication token");
        }

        String token = new String(decoded, getCredentialsCharset());

        int delim = token.indexOf(':');

        if (delim == -1) {
            throw new BadCredentialsException("Invalid basic authentication token");
        }
        return new String[] {token.substring(0, delim), token.substring(delim + 1)};
    }
    private String credentialsCharset = "UTF-8";
    protected String getCredentialsCharset(){
        return credentialsCharset;
    }
}
